<?php
/*依赖注入类*/
namespace util;

class Di implements \ArrayAccess{
    private $_bindings = array();//服务列表
    private $_instances = array();//已经实例化的服务
	private $params = array();//预定义参数数组
	private $currentService;//当前正在设置的服务

    //获取服务
    public function get($name,$params=array()){
		$this->emptyCurrentService();

        //先从已经实例化的列表中查找
        if(isset($this->_instances[$name])){
            return $this->_instances[$name];
        }

        //检测有没有注册该服务
        if(!isset($this->_bindings[$name])){
            return null;
        }

		//生成服务
        $obj = $this->instanceBuild($name,$params);

		//如果是共享服务，则写入_instances列表，下次直接取回
        if($this->_bindings[$name]['shared'] == true && $obj){
            $this->_instances[$name] = $obj;
        }

        return $obj;
    }

	//构建实例
	private function instanceBuild($name,$params){
		$concrete = $this->_bindings[$name]['class'];//对象具体注册内容
		$reliantArguments = $this->_bindings[$name]['reliantArguments'];//设置的依赖
		$methodConfigList = $this->_bindings[$name]['methodConfigList'];//预设的方法调用集 的配置
        $obj = null;
		//匿名函数方式
        if($concrete instanceof \Closure){
            $obj = call_user_func_array($concrete,$params);
        }elseif(is_string($concrete)){//字符串方式
			//若不带参数,则加载设置的依赖,将其作为默认参数
			if(empty($params)){
                $params = $this->loadReliantArguments($reliantArguments);
            }

			//若无依赖,直接生成目标对象
			if(empty($params)){
                $obj = new $concrete;
            }else{
                //带参数的类实例化，使用反射
                try {
                    $class = new \ReflectionClass($concrete);

                    //加载预处理的自定义参数
                    $params = $this->loadParameter($params);

                    //生成对象实例
                    $obj = $class->newInstanceArgs($params);
                } catch (\ReflectionException $e) {
                    echo $e->getMessage();
                }
            }
        }

		//运行预设的方法
		$this->invokeMethod($obj,$methodConfigList);

		return $obj;
	}


	//加载依赖,返回包含依赖实例的数组
	private function loadReliantArguments(Array $reliantArguments){
		$instance=array();
		foreach($reliantArguments as $argument ){
			if($argument instanceof DiMock){
				$diKey = $argument->getDiKey();
				$instance[] = $this->get($diKey);
			}else{
				$instance[] = $argument;
			}
		}

		return $instance;
	}

	//加载设置的预处理参数
	private function loadParameter($params){

		$newParams = array();
		foreach($params as $param ){
			if(is_string($param) && preg_match ("/%.*%/", $param)){
				$key = substr($param,1,strlen($param)-2);
                try {
                    $this->replaceParameter($newParams, $key, $param);
                } catch (\Exception $e) {
                    $e->getMessage();
                }
            }else{
				$newParams[] = $param;
			}
		}
		return $newParams;
	}

	//使用设置好的预处理参数值替换预处理参数
	private function replaceParameter(&$newParams,$key,$param){
		if(empty($this->params[$key])){
			$e = new \Exception('有未设置预处理值 : '.$param);
			echo $e->getMessage();
			exit();
		}else{
			$newParams[] = $this->params[$key];
		}
	}

	//调用预设的方法集
	private function invokeMethod($obj,$methodConfigList){
		foreach($methodConfigList as $config){
			//若未配置参数,直接运行
			if(empty($config['args'])){
                \call_user_func(array($obj,$config['name']));
            }else{
                //加载参数中依赖的对象
				$params = $this->loadReliantArguments($config['args']);

				//加载预处理的自定义参数
				$params = $this->loadParameter($params);

				\call_user_func_array(array($obj,$config['name']),$params);
            }

		}
	}

	//设置预处理参数的值
    public function setParameter($key, $val){
		$this->params[$key] = $val;
	}

	//检测是否已经绑定
    public function has($name){
        return isset($this->_bindings[$name]) or isset($this->_instances[$name]);
    }

    //卸载服务
    public function remove($name){
		$this->emptyCurrentService();
        unset($this->_bindings[$name],$this->_instances[$name]);
    }

	//置空正在设置的服务
	public function emptyCurrentService(){
		unset($this->currentService);
	}

    //设置服务
    public function set($name,$class){
        $this->_registerService($name, $class);
		return $this;
    }

    //设置共享服务
    public function setShared($name,$class){
        $this->_registerService($name, $class, true);
		return $this;
    }

    //注册服务
    private function _registerService($name,$class,$shared=false){
        $this->remove($name);
		$this->currentService = $name;
        if(!($class instanceof \Closure) && is_object($class)){
            $this->_instances[$name] = $class;
        }else{
			//Array(string:类名, bool:是否共享, array:依赖的参数, array:预调用的方法)
            $this->_bindings[$name] = array("class"=>$class,"shared"=>$shared,'reliantArguments'=>array(),'methodConfigList'=>array());
        }
    }

	//设置服务的依赖,$reliantArguments
	public function setArguments($reliantArguments=array()){
		$name = $this->currentService;
		if(!empty($this->_bindings[$name])){
			$this->_bindings[$name]['reliantArguments'] = $reliantArguments;
		}
		return $this;
    }

	//添加服务预设的方法调用,生成对象时自动运行该方法
	public function addMethodCall($methodName , Array $arguments=array()){
		$name = $this->currentService;
		if(!empty($this->_bindings[$name])){
			$methodConfig['name'] = $methodName;
			$methodConfig['args'] = $arguments;
			$this->_bindings[$name]['methodConfigList'][] = $methodConfig;
		}
		return $this;
	}

    //ArrayAccess接口,检测服务是否存在
    public function offsetExists($offset) {
        return $this->has($offset);
    }

    //ArrayAccess接口,以$di[$name]方式获取服务
    public function offsetGet($offset) {
        return $this->get($offset);
    }

    //ArrayAccess接口,以$di[$name]=$value方式注册服务，非共享
    public function offsetSet($offset, $value) {
        return $this->set($offset,$value);
    }

    //ArrayAccess接口,以unset($di[$name])方式卸载服务
    public function offsetUnset($offset) {
        return $this->remove($offset);
    }
}